#include <expander/Expander.h>
#include <folly/Conv.h>
#include <folly/logging/xlog.h>

namespace flashpp {

Expander::Expander(Pipeline& pipeline, Network& network) : network_(network) {
  addFiberTask([&pipeline, this]() mutable noexcept {
    while (1) {
      std::shared_ptr<DataPlaneUpdate> dpu;
      auto ticket = pipeline.blockingReadStage<1>(dpu);
      // XLOG(INFO) << "Got dpu at expander";
      // AtomicOverwrite ao;
      auto ao = std::make_shared<AtomicOverwrite>();
      process(*dpu, *ao);
      pipeline.blockingWriteStage<1>(ticket, std::move(ao));
    }
  });
}

void
Expander::process(DataPlaneUpdate& dpu, AtomicOverwrite& ao) {
  par(dpu, ao);
}

void
Expander::seq(DataPlaneUpdate& dpu, AtomicOverwrite& ao) {
  ao.device = dpu.deviceName;
  auto& device = network_.nameToDevice(dpu.deviceName);

  // all insertion is expanded
  auto& expanded = dpu.added;

  HeaderSpace fwd;
  auto oit = device.rules().begin();
  auto nit = expanded.begin();
  while (nit != expanded.end()) {
    if (fwd.isTrue()) {
      break;
    }
    // skip origin rules with high priorities
    // if (oit != device.rules().end() && oit->second.priority > nit->priority)
    // {
    //   fwd |= oit->second.hs;
    //   oit++;
    //   continue;
    // }
    nit->get()->hit = nit->get()->hs - fwd;
    if (!nit->get()->hit.isFalse()) {
      ao.overwrites.emplace_back(nit->get()->hit, nit->get()->port);
      // ao.overwrites.push_back(std::move(std::make_pair(nit->hit,
      // nit->port))); fwd = fwd | nit->hit;
      fwd |= nit->get()->hit;
    }
    nit++;
  }
  // if (false) {
  //   for (auto& rule : dpu.added) {
  //     device.rules()[rule->priority] = std::move(rule);
  //   }
  // }

  // APKeep approach
  // for (auto& rule : dpu.added) {
  //   rule.hit = rule.hs;
  //   for (auto& [pri, orule] : device.rules()) {
  //     if (pri > rule.priority && !((rule.hit & orule.hit).isFalse())) {
  //       rule.hit = rule.hit & !(orule.hit);
  //     }
  //     if (pri < rule.priority && !((rule.hit & orule.hit).isFalse())) {
  //       orule.hit = orule.hit & !(rule.hit);
  //     }
  //   }
  // }
}

void
Expander::par(DataPlaneUpdate& dpu, AtomicOverwrite& ao) {
  ao.device = dpu.deviceName;
  auto& device = network_.nameToDevice(dpu.deviceName);
  std::for_each(
      // std::execution::par,
      std::execution::par_unseq,
      dpu.added.begin(),
      dpu.added.end(),
      [&](auto rule) {
        std::vector<std::shared_ptr<Rule>> overlapSet;
        device.getOverlapSet(rule, overlapSet);
        // XLOG(INFO) << "num overlap: " << overlapSet.size();
        std::vector<HeaderSpace> hss;
        // HeaderSpace fwd;
        for (auto& r : overlapSet) {
          // fwd |= r->hs;
          hss.push_back(r->hs);
        }

        auto fwd = std::reduce(
            std::execution::par_unseq,
            hss.begin(),
            hss.end(),
            HeaderSpace::hsEmpty(),
            [](auto x, auto y) { return x | y; });
        rule->hit = rule->hs - fwd;
        if (!rule->hit.isFalse()) {
          // const std::lock_guard<std::mutex> lock(mutex_);
          ao.overwrites.emplace_back(rule->hit, rule->port);
        }
      });
}

} // namespace flashpp